<?php

namespace app\bll;

use app\validate\UcenterMenu as ValidateUcenterMenu;
use BlueM\Tree;
use BlueM\Tree\Node;
use BlueM\Tree\Serializer\HierarchicalTreeJsonSerializer;
use Exception;
use support\Db;
use Throwable;

class UcenterMenu
{
    /**
     * 查找菜单
     *
     * @param integer $id
     * @return array
     */
    public function find(int $id): array
    {
        $data =  Db::table('frame_ucenter_menu')
            ->where('id', '=', $id)
            ->first();
        return $data === null ? [] : get_object_vars($data);
    }

    /**
     * 根据菜单标识查找菜单
     *
     * @param string $ident
     * @return array
     */
    public function findByIdent(string $ident): array
    {
        $data =  Db::table('frame_ucenter_menu')
            ->where('ident', '=', $ident)
            ->first();
        return $data === null ? [] : get_object_vars($data);
    }


    /**
     * 查询菜单
     *
     * @param string $title
     * @param integer|null $type
     * @param integer|null $pid
     * @return array
     */
    public function select(
        string $title = '',
        ?int $type = null,
        ?int $pid = null,
        array $ident = [],
        array $fields = ['*']
    ): array {
        return  Db::table('frame_ucenter_menu')
            ->when($title != '', function ($query) use ($title) {
                return $query->where('title', 'like', '%' . $title . '%');
            })
            ->when($type !== null, function ($query) use ($type) {
                return $query->where('type', '=', $type);
            })
            ->when($pid !== null, function ($query) use ($pid) {
                return $query->where('pid', '=', $pid);
            })

            ->when($ident, function ($query) use ($ident) {
                return $query->whereIn('ident',  $ident);
            })
            ->orderBy('sort')
            ->get($fields)
            ->map(function ($value) {
                return (array)$value;
            })
            ->toArray();
    }


    /**
     * 添加一个菜单
     *
     * @param array $data
     * @return integer
     */
    public function add(array $data): int
    {

        //验证
        $validateUcenterMenu = new ValidateUcenterMenu();
        if (!$validateUcenterMenu->check($data)) {
            throw new Exception($validateUcenterMenu->getSimpleError());
        }

        $menuinfo = $this->findByIdent($data['ident']);
        if ($menuinfo) {
            throw new Exception('标识已存在');
        }
        return Db::table('frame_ucenter_menu')
            ->insertGetId($data);
    }


    /**
     * 修改菜单
     *
     * @param integer $id
     * @param array $data
     * @return void
     */
    public function edit(int $id, array $data)
    {
        //获取原先菜单信息
        $oldmenu = $this->find($id);
        //验证
        $validateUcenterMenu = new ValidateUcenterMenu();
        if (!$validateUcenterMenu->check($data)) {
            throw new Exception($validateUcenterMenu->getSimpleError());
        }


        $menuinfo = $this->findByIdent($data['ident']);
        if ($menuinfo && $menuinfo['id'] != $id) {
            throw new Exception('标识已存在');
        }
        Db::beginTransaction();
        try {
            Db::table('frame_ucenter_menu')
                ->where('id', '=', $id)
                ->update($data);

            //如果菜单标识改变 则更新子级菜单标识
            if ($oldmenu['ident'] != $data['ident']) {
                Db::table('frame_ucenter_menu')
                    ->where('pident', '=', $oldmenu['ident'])
                    ->update([
                        'pident' => $data['ident']
                    ]);
            }
            Db::commit();
        } catch (Throwable $ex) {
            Db::rollBack();
            throw $ex;
        }
    }

    /**
     * 删除菜单 包含子级
     *
     * @param integer $id
     * @return void
     */
    public function delete(int $id)
    {
        Db::beginTransaction();
        try {
            $menuinfo    = $this->find($id);
            $descendants = $this->getDescendantsAndSelfByIdent($menuinfo['ident']);
            $idents         = array_column($descendants, 'ident');

            Db::table('frame_ucenter_menu')
                ->whereIn('ident',  $idents)
                ->delete();
            Db::commit();
        } catch (Exception $ex) {
            Db::rollBack();
            throw $ex;
        }
    }

    /**
     * 返回菜单的树形对象
     *
     * @param integer|null $type
     * @param integer|null $pid
     * @return Tree
     */
    public function tree(
        ?int $type = null,
        ?int $belong = null,
        array $ident = []
    ): Tree {
        $data = $this->select(
            type: $type,
            ident: $ident
        );
        $tree = new Tree(
            $data,
            [
                'rootId'         => '',
                'id'             => 'ident',
                'parent'         => 'pident'
            ]
        );
        return $tree;
    }


    /**
     * 根据任何菜单数据返回Tree对象
     *
     * @param array $data
     * @return Tree
     */
    public function treeByAnyData(array $data): Tree
    {
        $tree = new Tree(
            $data,
            [
                'rootId'         => '',
                'id'             => 'ident',
                'parent'         => 'pident'
            ]
        );
        return $tree;
    }




    /**
     * 返回菜单的树形Node对象
     *
     * @param integer|null $type
     * @param integer|null $pid
     * @param array $ids
     * @return array
     */
    public function nodes(?int $type = null, array $ident = []): array
    {
        $tree = $this->tree(type: $type, ident: $ident);
        return $tree->getNodes();
    }

    /**
     * 根据路径返回菜单
     *
     * @param string $url
     * @return array
     */
    public function findByUrl(string $url): array|null
    {
        $menu =  Db::table('frame_ucenter_menu')
            ->where('url', '=', $url)
            ->first();
        return $menu === null ? [] : get_object_vars($menu);
    }

    /**
     * 根据ID 获取单个菜单节点
     * @param array $menu 菜单
     * @return Node
     */
    public function getNodeByIdent(string $ident): Node
    {
        $tree = $this->tree();
        return $tree->getNodeById($ident);
    }


    /**
     * 根据菜单 获取单个菜单节点
     * @param array $menu 菜单
     * @return Node
     */
    public function getNodeByMenu(array $menu): Node
    {
        $tree = $this->tree();
        return $tree->getNodeById($menu['ident']);
    }

    /**
     * 获取自己和自己的父级
     *
     * @param array $menu
     * @return array
     */
    public function getAncestorsAndSelf(array $menu): array
    {
        $node              = $this->getNodeByMenu($menu);
        $ancestorsPlusSelf = $node->getAncestorsAndSelf();
        $parents           = [];
        foreach ($ancestorsPlusSelf as $ancestor) {
            $parents[] = $ancestor->toArray();
        }
        krsort($parents);
        return array_values($parents);
    }

    /**
     * 获取所有子集
     *
     * @param array $menu
     * @return array
     */
    public function getDescendantsAndSelf(array $menu): array
    {
        $node              = $this->getNodeByMenu($menu);
        $descendantsAndSelf = $node->getDescendantsAndSelf();

        $childrens           = [];
        foreach ($descendantsAndSelf as $descendants) {
            $childrens[] = $descendants->toArray();
        }
        return array_values($childrens);
    }

    /**
     * 根据node获取自己和父级
     *
     * @param Node $node
     * @return array
     */
    public function getAncestorsAndSelfByNode(Node $node): array
    {
        $ancestorsPlusSelf = $node->getAncestorsAndSelf();
        $parents           = [];
        foreach ($ancestorsPlusSelf as $ancestor) {
            $parents[] = $ancestor->toArray();
        }
        krsort($parents);
        return array_values($parents);
    }


    /**
     * 获取所有子集
     *
     * @param array $menu
     * @return array
     */
    public function getDescendantsAndSelfByIdent(string $ident): array
    {
        $node               = $this->getNodeByIdent($ident);
        $descendantsAndSelf = $node->getDescendantsAndSelf();

        $childrens           = [];
        foreach ($descendantsAndSelf as $descendants) {
            $childrens[] = $descendants->toArray();
        }
        return array_values($childrens);
    }

    /**
     * 根据Tree对象 获取单个菜单节点
     *  @param integer $id
     * @param Tree $tree 菜单
     * @return Node
     */
    public function getNodeByTree(string $ident, Tree $tree)
    {
        return $tree->getNodeById($ident);
    }

    /**
     * 菜单层级关系
     *
     * @return array|string
     */
    public function cascader(
        array $fields = ['*'],
        bool $formatjson = true,
        ?int $type = null
    ): array|string {

        $tree = new Tree(
            $this->select(
                fields: $fields,
                type: $type
            ),
            [
                'rootId'         => '',
                'id'             => 'ident',
                'parent'         => 'pident',
                'jsonSerializer' => new HierarchicalTreeJsonSerializer()
            ]
        );

        return $formatjson ? json_encode($tree, JSON_UNESCAPED_UNICODE) : $tree;
    }
}
